package ZCrIS;

# $Id: ZCrIS.pm 5 2007-12-28 17:35:50Z landofzohocreator $
# $Date: 2007-12-28 17:35:50 +0000 (Fri, 28 Dec 2007) $
# $Revision: 5 $
# $HeadURL: http://zcris.googlecode.com/svn/trunk/perl5lib/lib/ZCrIS.pm $
# $Author: landofzohocreator $

# Land of Zoho Creator (LoZC)
# http://landofzohocreator.com

# Class to support the processing of
# incoming mail messages for use with
# Zoho Creqtor (http://creator.zoho.com)

use strict;

use Data::Dumper;
use Email::Filter;
use Log::Tiny;
use LWP::UserAgent;
use Mail::Mailer;
use XML::Simple;

our $AUTOLOAD;

#
# Invoke processIncomingMessage
# from a mail processing script
#

sub processIncomingMessage {
  my ($class, $customFile) = @_;
  my $self = {};

  $self->{_native} = {
    incomingMessage => undef,
    payload         => undef,
    webClient       => undef,
    xmlParser       => undef,
  };

  bless $self, $class;

  $self->_getXMLParser;

  my $customizations = $self->xmlParser->XMLin(
    $customFile, ValueAttr => ['value']
  );
  $self->config($customizations->{directives});
  $self->logMessage($customizations->{logMessages});

  $self->_getLogHandle;
  $self->log->DEBUG('Object created.') if $self->debugging;


  $self->_parseIncomingMessage;
  $self->_executeCommand;
}

#
# Methods for internal use as well as
# within plugin packages
#

sub addWebClient {
  my $self = shift;
  $self->log->DEBUG('Adding web client capabilities.') if $self->debugging;
  $self->webClient(LWP::UserAgent->new) or
    die 'Could not acquire web client: ' . $!;
  $self->webClient->timeout(10);
  return $self;
}

sub config {
  my ($self, $arg) = @_;
  if (ref($arg)) {
    $self->{config} = $arg;
  }
  else {
    return $self->{config}{$arg};
  }
}

sub debugging {
  my $self = shift;
  return 1 if $self->config('debugging') =~ /true/i;
}

sub fail {
  my ($self, $message, $arg) = @_;
  $self->log->ERROR($self->logMessage($message) . $arg);
  $self->incomingMessage->reject($self->logMessage($message) . $arg );
  exit;
}

sub log {
  my $self = shift;
  return $self->{log};
}

sub logMessage {
  my ($self, $arg) = @_;
  if (ref($arg)) {
    $self->{logMessages} = $arg;
  }
  else {
    return $self->{logMessages}{$arg};
  }
}

sub sendOutgoingMessage {
  my ($self, $outgoingHeaders, $body) = @_;
  $outgoingHeaders->{From} = $self->config('replyIdentity');
  $outgoingHeaders->{'Reply-To'} = $self->config('replyIdentity');

  $self->log->DEBUG("Message body is: \n" . $body);

  $self->log->INFO($self->logMessage('replyingTo') . $outgoingHeaders->{To});

  my $outgoingMessage = new Mail::Mailer 'sendmail';
  $outgoingMessage->open($outgoingHeaders);
  print $outgoingMessage $body;
  $outgoingMessage->close;
  $self->log->DEBUG('Message sent.') if $self->debugging;
  $self->incomingMessage->accept($self->config('acceptMailbox'));
}

sub trim {
  my ($self, $string) = @_;
  $string    =~ s/^\s+//;
  $string    =~ s/\s+$//;
  return $string;
}

#
# Private methods
#

sub _executeCommand {
  my $self = shift;
  my $command = lc($self->trim($self->incomingMessage->subject));
  $command =~ s/\{spam\?\} //;
  # trap 'Returned mail' messages for processing
  $command = 'returned mail' if 
    ($command =~ /returned mail/i) || 
    ($command =~ /error occured while adding record/i) ||
    ($command =~ /authorization failed/i);
  $self->log->DEBUG(qq|Command is "$command".|) if $self->debugging;
  $command =~ s/ /_/g;
  $self->log->DEBUG(qq|Executing "$command".|) if $self->debugging;
  $self->$command;
}

sub _getLogHandle {
  my $self = shift;
  $self->{log} = Log::Tiny->new($self->config('logFile'), '[%t] [%c] %m%n') or
    die 'Could not acquire log: ' . Log::Tiny->errstr;
  return $self;
}

sub _getXMLParser {
  my $self=shift;
  $self->xmlParser(XML::Simple->new);
  return $self;
}

sub _parseIncomingMessage {
  my $self = shift;

  $self->incomingMessage(Email::Filter->new);
  $self->incomingMessage->exit(1);

  $self->log->DEBUG('Incoming message received.') if $self->debugging;

  my %payload = ();
  my @body = split /\n/, $self->incomingMessage->body;
  for my $line(@body) {
    next unless $line =~ /\|/;
    my ($name, $value) = split /\|/, $line;
    ($name, $value) = (lc($self->trim($name)), lc($self->trim($value)));
    $payload{$name} = $value;
  }

  $self->payload(\%payload);

  $self->log->DEBUG('Incoming message parsed.') if $self->debugging;

  return $self;
}

#
# Overridden built-in methods
#

sub AUTOLOAD {
  my $self = shift;

  my $type = ref($self) or die "$self is not an object";
  my $name = $AUTOLOAD;
  $name =~ s/.*://;   # strip fully-qualified portion

  if (exists $self->{_native}{$name} ) {
    if (@_) {
      return $self->{$name} = shift;
    }
    else {
      return $self->{$name};
    }
  }
  else {
    $self->log->DEBUG(qq|Searching for plugin "$name"...|) if $self->debugging;
    eval "require ZCrIS::$name";
    $self->fail('unrecognizedCommand') if $@;

    eval "ZCrIS::${name}->run(\$self);";
    $self->fail('serviceError', $@) if $@;
  }
}  

sub DESTROY {
  my $self = shift;
  $self->log->DEBUG('Destroying ' . __PACKAGE__ . ' object.');
}

1;
